/*
 * Hello Minecraft! Launcher
 * Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package org.jackhuang.hmcl.gradle.l10n;

import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.jackhuang.hmcl.gradle.utils.PropertiesUtils;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/// @author Glavo
public abstract class UpsideDownTranslate extends DefaultTask {

    static final Locale EN_QABS = Locale.forLanguageTag("en-Qabs");

    private static final Map<String, String> PROPERTIES = Map.of(
            "datetime.format", "MMM d, yyyy, h:mm:ss a"
    );

    @InputFile
    public abstract RegularFileProperty getInputFile();

    @OutputFile
    public abstract RegularFileProperty getOutputFile();

    @TaskAction
    public void run() throws IOException {
        Path inputFile = getInputFile().get().getAsFile().toPath();
        Path outputFile = getOutputFile().get().getAsFile().toPath();

        Properties english = PropertiesUtils.load(inputFile);

        Properties output = new Properties();
        Translator translator = new Translator();
        english.forEach((k, v) -> {
            if (PROPERTIES.containsKey(k.toString())) {
                output.setProperty(k.toString(), PROPERTIES.get(k.toString()));
            } else {
                output.put(k, translator.translate(v.toString()));
            }
        });

        Files.createDirectories(outputFile.getParent());
        try (var writer = Files.newBufferedWriter(outputFile)) {
            output.store(writer, "This file is automatically generated, please do not modify it manually");
        }
    }

    static final class Translator {
        private static final Map<Integer, Integer> MAPPER = loadMap();

        private static Map<Integer, Integer> loadMap() {
            var map = new LinkedHashMap<Integer, Integer>();

            InputStream inputStream = UpsideDownTranslate.class.getResourceAsStream("upside_down.txt");
            if (inputStream != null) {
                try (inputStream) {
                    new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {
                        if (line.isBlank() || line.startsWith("#"))
                            return;

                        if (line.length() != 2) {
                            throw new GradleException("Invalid line: " + line);
                        }

                        map.put((int) line.charAt(0), (int) line.charAt(1));
                    });
                } catch (IOException e) {
                    throw new GradleException("Failed to load upside_down.txt", e);
                }
            } else {
                throw new GradleException("upside_down.txt not found");
            }
            return Collections.unmodifiableMap(map);
        }

        private static final Pattern FORMAT_PATTERN = Pattern.compile("^%(\\d\\$)?(\\d+)?(\\.\\d+)?([sdf])");
        private static final Pattern XML_TAG_PATTERN = Pattern.compile("^<(?<tag>[a-zA-Z]+)( href=\"[^\"]*\")?>");

        private final StringBuilder resultBuilder = new StringBuilder();

        private void appendToLineBuilder(String input) {
            for (int i = 0; i < input.length(); ) {
                int ch = input.codePointAt(i);

                if (ch == '%') {
                    Matcher matcher = FORMAT_PATTERN.matcher(input).region(i, input.length());
                    if (matcher.find()) {
                        String formatString = matcher.group();
                        resultBuilder.insert(0, formatString);
                        i += formatString.length();
                        continue;
                    }
                } else if (ch == '<') {
                    Matcher matcher = XML_TAG_PATTERN.matcher(input).region(i, input.length());
                    if (matcher.find()) {
                        String beginTag = matcher.group();
                        String endTag = "</" + matcher.group(1) + ">";

                        int endTagOffset = input.indexOf(endTag, i + beginTag.length());
                        if (endTagOffset > 0) {
                            resultBuilder.insert(0, endTag);
                            appendToLineBuilder(input.substring(i + beginTag.length(), endTagOffset));
                            resultBuilder.insert(0, beginTag);

                            i = endTagOffset + endTag.length();
                            continue;
                        }
                    }
                }

                int udCh = MAPPER.getOrDefault(ch, ch);
                if (Character.isBmpCodePoint(udCh)) {
                    resultBuilder.insert(0, (char) udCh);
                } else {
                    resultBuilder.insert(0, Character.toChars(udCh));
                }

                i += Character.charCount(ch);
            }
        }

        String translate(String input) {
            resultBuilder.setLength(0);
            appendToLineBuilder(input);
            return resultBuilder.toString();
        }
    }
}
